#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#define SIZE 1024
#define MAX_ARGC 64
#define SEP " "

// 全局进程
char *argv[MAX_ARGC];
//工作目录
char pwd[SIZE];
//环境变量
char env[SIZE];
//进程退出信息
int lastcode = 0;

// 获取主机名
const char *HostName()
{
    char *hostname = getenv("HOSTNAME");
    if (hostname)
        return hostname;
    else
        return "None";
}

// 获取当前登录的用户名
const char *UserName()
{
    char *username = getenv("USER");
    if (username)
        return username;
    else
        return "None";
}

// 获取当前工作目录
const char *CurrentWorkDir()
{
    char *currentworkdir = getenv("PWD");
    if (currentworkdir)
        return currentworkdir;
    else
        return "None";
}

// 获取用户输入的命令
void Interactive(char out[], int size)
{
    fgets(out, SIZE, stdin);     // stdin是标准输入流，意思就是从键盘获取数据保存到commandline中
    out[strlen(out) - 1] = '\0'; // 因为fgets会读取换行符，所以这步我们去掉换行。
}

// 对字符串进行分割
void Split(char in[])
{
    int i = 0;
    argv[i++] = strtok(in, SEP); // 对字符串commandline以空格作为分隔符进行切割，"ls -a -l"
    while (argv[i++] = strtok(NULL, SEP)); // 进行第二次切割时，strtok第一个参数需要传入NULL。并且最后会填入NULL
    if(strcmp(argv[0], "ls") == 0)
    {
        argv[i - 1] = "--color";//即在字符串末尾加上--color选项
        argv[i] = NULL;
    }
}

void Execute()
{
    // 因为程序替换后，不会在执行之后的代码，所以这里创建子进程去执行最合适
    pid_t id = fork();
    if (id == 0)
    {
        // 子进程通过程序替换执行命令
        execvp(argv[0], argv);
        exit(1);
    }
    // 父进程进行等待,并查看退出信息
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid == id)
    {
        //使用宏解析出退出码
        lastcode = WEXITSTATUS(status);
    }
   // printf("run done,rid: %d\n", rid);
}

char* Home()
{
    return getenv("HOME");
}

int BuildinCmd()
{
    int ret = 0;
    //检查是否为内建命令，是 1，否 0
    if(strcmp("cd",argv[0]) == 0)
    {
        //执行
        ret = 1;
        char* target = argv[1];
        if(!target) target = Home();//如果只输入cd，则argv[1]的值为0,则会进入if语句，默认跳转到Home()工作目录
        //通过系统调用chdir，改变当前工作目录
        chdir(target);
        char temp[1024];
        getcwd(temp,1024);
        //虽然具体的工作目录变了，但是命令行提示符中工作目录我们没有实时更新，所以还没有变
        //此时需要处理一下,修改PWD环境变量，这样下次循环时，命令行提示符获取的就是当前路径。
        snprintf(pwd,SIZE,"PWD=%s",temp);
        putenv(pwd);
    }
    else if(strcmp("export",argv[0]) == 0)
    {
        ret = 1;
        if(argv[1])
        {
            strcpy(env,argv[1]);
            putenv(env);
        }
    }
    else if(strcmp("echo",argv[0]) == 0)
    {
        ret = 1;
        if(argv[1] == NULL)
        {
            printf("\n");
        }
        else{
            if(argv[1][0] == '$')//$用于查看环境变量的值
            {
                if(argv[1][1] == '?')//$?:查看进程退出码
                {
                    printf("%d\n",lastcode);
                    lastcode = 0;
                }
                else
                {
                    char* e = getenv(argv[1]+1);//echo $PWD
                    if(e) printf("%s\n",e);
                }
            }
            else{
                //如果不是以$开头，则正常打印内容
                printf("%s\n",argv[1]);
            }
        }
    }
    return ret;
}

int main()
{
    while (1)
    {
        // 1、输出提示符
        printf("[%s@%s %s]$ ", UserName(), HostName(), CurrentWorkDir());

        // 2、获取用户输入的命令
        char commandline[SIZE];
        Interactive(commandline, SIZE);

        // 3、对命令行字符串进行切割
        Split(commandline);

        // 4、处理内建命令
        int n = BuildinCmd();
        if(n)continue;
        // 5、执行分割好的命令
        Execute();
    }
    return 0;
}